package com.leonardo.persistenceme.metadatahandlers;

import java.io.InputStream;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import com.leonardo.persistenceme.exceptions.XmlReadingException;
import com.leonardo.persistenceme.metadatahandlers.taghandlers.BeanTagHandler;
import com.leonardo.persistenceme.metadatahandlers.taghandlers.FieldTagHandler;
import com.leonardo.persistenceme.metadatahandlers.taghandlers.PersistencemeTagHandler;
import com.leonardo.persistenceme.metadatahandlers.taghandlers.TagHandler;

public class PersistenceXmlReader extends PersistenceMetadataReader {

	private static final String source = "/META-INF/persistenceme.xml"; 	
	private static final Class[] allDefaultHandlers = { BeanTagHandler.class, 
		FieldTagHandler.class, PersistencemeTagHandler.class };
	
	private int nBeans = 0;
	private Vector beans = new Vector();
	
	public PersistenceXmlReader() throws Exception {
		SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
		InputStream inputStream = PersistenceXmlReader.class.getResourceAsStream(source);
		saxParser.parse(inputStream, new FormHandler());
	}
		
	public void reset() {
		nBeans = 0;
	}
	
	public boolean hasMoreBeans() {
		return nBeans < beans.size();
	}
	
	public PersistenceMetadataContainer next() {
		return (PersistenceMetadataContainer) beans.elementAt(nBeans++);
	}
	
	public Hashtable getMetadataContainers() {
		Hashtable customConfigs = new Hashtable();
		
		{/*System.out.println("Putting beans in configuration");*/}
		reset();
		while(hasMoreBeans()) {
			PersistenceMetadataContainer bean = next();
			{/*System.out.println("	Bean: " + bean.getId());*/}
			customConfigs.put(bean.getId(), bean);
		}
		return customConfigs;
	}
	
	public class FormHandler extends DefaultHandler {
		private final String DOCUMENT_TAG = "persistenceme";
		
		private PersistenceMetadataContainer currentContainer;
		private Stack tagNameStack = new Stack();
		private Stack handlerStack = new Stack();
		
		private Hashtable handlers = new Hashtable(allDefaultHandlers.length);
		
		public FormHandler() {
			for(int i = 0; i < allDefaultHandlers.length; i++)
				try {
					TagHandler handler = (TagHandler) allDefaultHandlers[i].newInstance();
					handlers.put(handler.getTagName(), allDefaultHandlers[i]);
				} catch(Exception ex) {
					//never happens
					ex.printStackTrace();
				}
		}
		
		private TagHandler getTagHandler(String tagName) {
			try {
				TagHandler handler = (TagHandler) (((Class)handlers.get(tagName)).newInstance());
				handler.setOwner(this);
				return handler;
			} catch (Exception e) {
				// never happens
				e.printStackTrace();
			} 
			return null;
		}
		
		public void startDocument() {
			{/*System.out.println("Start document.");*/}
		}
		public void endDocument() {
			if(!tagNameStack.isEmpty())
				{/*System.out.println("[WARN] Peek tag: " + (String) tagNameStack.peek());*/}
			{/*System.out.println("End document.");*/}
		}

		public void startElement(String uri, String localName, String tagName, Attributes attributes) {
			startTagValidation(tagName);
			
			TagHandler nextHandler = getTagHandler(tagName);
			handlerStack.push(nextHandler);
			nextHandler.startTag(attributes);
			tagNameStack.push(tagName);
		}
		
		public void characters(char[] ch, int start, int lenght) {
			TagHandler handler = (TagHandler) handlerStack.peek();	
			handler.characters(ch, start, lenght);
		}
		
		public void endElement(String uri, String localName, String tagName) {
			endTagValidation(tagName);
			tagNameStack.pop();
			((TagHandler) handlerStack.pop()).endTag(); 
		}
		
		public void fatalError(SAXParseException ex) {
			throw new XmlReadingException("Error reading xml. Check if your not closing a tag " +
					"without open it before." + ex.getMessage());
		}
		
		private void startTagValidation(String tagName) {
			{/*System.out.println("init start validation");*/}
			if(!DOCUMENT_TAG.equals(tagName) && !checkDocumentOpened())
				throw new XmlReadingException("Missing " + DOCUMENT_TAG + " tag");
			if(!handlers.containsKey(tagName))
				throw new XmlReadingException("Found invalid tag " + tagName);
		}
		
		private void endTagValidation(String tagName) {
			if(!checkDocumentOpened() || !tagName.equals(tagNameStack.peek()))
				throw new XmlReadingException("Invalid closing tag " + tagName);
		}
		
		private boolean checkDocumentOpened() {
			return tagNameStack.contains(DOCUMENT_TAG);
		}
						
		public void addField(String field) {
			currentContainer.addField(field);
		}
		
		public void setType(String type) {
			currentContainer.setType(type);
		}
				
		public void newBean(String id) {
			currentContainer = new PersistenceMetadataContainer(id);
		}
		
		public void addCurrentBean() {
			beans.addElement(currentContainer);
		}
		
		public boolean isTagOpen(String tag) {
			return tagNameStack.contains(tag);
		}
		
	}

}
